﻿using System;
using System.Collections;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TestPrint.PrintHelp
{
    public class PrintManager : IDisposable
    {
        private string _lastImageType;
        private IPagePrinter _PagePrinter;

        public void PrintPage(string imageType, PrinterSettings printerSettings, PrintPageEventHandler pageEventHandler)
        {
            if (_lastImageType != imageType)
            {
                if (_PagePrinter != null)
                    _PagePrinter.Dispose();

                switch (imageType)
                {
                    case "EPL":
                    case "EPL2":
                        _PagePrinter = new RawPagePrinter();
                        break;
                    default:
                        _PagePrinter = new ImagePagePrinter();
                        break;
                }
                _lastImageType = imageType;
            }

            _PagePrinter.PrintPage(printerSettings, pageEventHandler);
        }

        public bool WritePageBytes(byte[] pBytes)
        {
            if (_PagePrinter != null)
                return _PagePrinter.WritePageBytes(pBytes);
            return false;
        }

        public void Dispose()
        {
            if (_PagePrinter != null)
                _PagePrinter.Dispose();

            _lastImageType = null;
        }
    }

    public interface IPagePrinter : IDisposable
    {
        void PrintPage(PrinterSettings printerSettings, PrintPageEventHandler pageEventHandler);
        bool WritePageBytes(byte[] pBytes);
    }

    public class ImagePagePrinter : IPagePrinter
    {
        private readonly Hashtable _htPrints = new Hashtable();

        public void PrintPage(PrinterSettings printerSettings, PrintPageEventHandler pageEventHandler)
        {
            var controller = (ImagePrintController)_htPrints[printerSettings.PrinterName];
            if (controller == null)
            {
                controller = new ImagePrintController();
                _htPrints.Add(printerSettings.PrinterName, controller);
            }

            var printDocument = new PrintDocument();
            printDocument.PrintController = controller;
            AdjustPageSize(printerSettings, printDocument.PrinterSettings);
            printDocument.PrinterSettings = printerSettings;
            printDocument.QueryPageSettings += (sender, e) =>
            {
                e.PageSettings = ((PrintDocument)sender).DefaultPageSettings;
            };
            printDocument.PrintPage += pageEventHandler;
            printDocument.Print();
        }

        private void AdjustPageSize(PrinterSettings expectSettings, PrinterSettings devicerSetting)
        {
            if (expectSettings != null)
            {
                devicerSetting.PrinterName = expectSettings.PrinterName;
                var expectPageSize = expectSettings.DefaultPageSettings.PaperSize;
                if (expectPageSize.Width != 0 || expectPageSize.Height != 0)
                {
                    PaperSize printPaperSize = devicerSetting.DefaultPageSettings.PaperSize;
                    if (printPaperSize.Width < expectPageSize.Width || printPaperSize.Height < expectPageSize.Height)
                    {
                        PaperSize ps = new PaperSize("custom", expectPageSize.Width, expectPageSize.Height);
                        ps.RawKind = 256;
                        devicerSetting.DefaultPageSettings.PaperSize = ps;
                    }
                    else
                        expectSettings.DefaultPageSettings.PaperSize = devicerSetting.DefaultPageSettings.PaperSize;
                }
            }
        }

        public void Dispose()
        {
            try
            {
                foreach (ImagePrintController printController in _htPrints.Values)
                {
                    printController.Dispose();
                }
                _htPrints.Clear();
            }
            catch
            {
            }

        }

        public bool WritePageBytes(byte[] pBytes)
        {
            throw new NotImplementedException();
        }
    }

    public class ImagePrintController : StandardPrintController, IDisposable
    {
        PrintDocument _document;
        PrintEventArgs _printEventArgs;
        public override void OnStartPrint(PrintDocument document, PrintEventArgs e)
        {
            if (_document == null)
            {
                base.OnStartPrint(document, e);
                _document = document;
                _printEventArgs = e;
            }
        }

        public override void OnEndPrint(PrintDocument document, PrintEventArgs e)
        {
        }

        public void Dispose()
        {
            if (_document != null && _printEventArgs != null)
                base.OnEndPrint(_document, _printEventArgs);
        }
    }

    public class RawPagePrinter : IPagePrinter
    {
        private readonly Hashtable _htPrints = new Hashtable();
        IntPtr _hPrinter;

        public void PrintPage(PrinterSettings printerSettings, PrintPageEventHandler pageEventHandler)
        {
            var _printArgs = _htPrints[printerSettings.PrinterName];
            if (_printArgs == null)
            {
                IntPtr hPrinter;
                WinDrvPort.DOCINFOA di = new WinDrvPort.DOCINFOA();
                di.pDocName = "Printer Command";
                di.pDataType = "RAW";
                if (WinDrvPort.OpenPrinter(printerSettings.PrinterName, out hPrinter, IntPtr.Zero))
                {
                    var flag = WinDrvPort.StartDocPrinter(hPrinter, 1, di);
                    flag = WinDrvPort.StartPagePrinter(hPrinter);
                    _htPrints.Add(printerSettings.PrinterName, hPrinter);
                    _hPrinter = hPrinter;
                    Console.WriteLine(flag);
                }
            }

            pageEventHandler(null, null);
        }

        public bool WritePageBytes(byte[] pBytes)
        {
            int errorCode;
            bool flag = false;
            int dwWritten = 0;
            int count = pBytes.Length;
            var destination = Marshal.AllocCoTaskMem(count);
            Marshal.Copy(pBytes, 0, destination, count);
            flag = WinDrvPort.WritePrinter(_hPrinter, destination, count, out dwWritten);
            Marshal.FreeCoTaskMem(destination);

            if (!flag)
            {
                errorCode = Marshal.GetLastWin32Error();
                Console.WriteLine(errorCode);
            }
            return flag;
        }

        public void Dispose()
        {
            try
            {
                foreach (IntPtr hPrinter in _htPrints.Values)
                {
                    WinDrvPort.EndPagePrinter(_hPrinter);
                    WinDrvPort.EndDocPrinter(hPrinter);
                    WinDrvPort.ClosePrinter(hPrinter);
                }
            }
            catch
            {
            }
        }
    }
}
